package com.mx.cofee;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.ImageIcon;

public class Board extends JPanel implements ActionListener {

	private int height = 15;
	private int width = 10;
	private int points = 0;
	private int curX = 0;
	private int curY = (height - 1) * Block.SIZE;
	private boolean isPaused = false;
	private boolean isRunning = false;
	private List<Element> candidates;

	private static enum Direction {
		UP, RIGHT, DOWN, LEFT
	};

	private static enum Shape {
		H_LINE, V_LINE
	};

	private JLabel statusBar;
	private Block blockSelected;
	private List<Block> blockList;
	private Block[][] blocks = new Block[height][width];
	private boolean[][] table = new boolean[height][width];
	private Cursor sight;

	Timer timer;
	Timer keyTimer;
	Timer newsTimer;

	public Board(Gtris parent) {
		blockList = new ArrayList<Block>();
		setFocusable(true);
		statusBar = parent.getStatusBar();
		setBorder(BorderFactory.createLineBorder(Color.black));
		createCursor();
		candidates = new ArrayList<Element>();

		setRandomBlocks();

		addKeyListener(new GAdapter());

		ActionListener actionListener = new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				oneLineDown();

			}
		};
		timer = new Timer(250, actionListener);
		keyTimer = new Timer(0, this);
		keyTimer.start();

		ActionListener newsActionListener = new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				newBlocks();
			}
		};
		newsTimer = new Timer(2000, newsActionListener);
		newsTimer.start();
	}

	public void createCursor() {
		sight = new Cursor();
		sight.setY(curY);
		sight.setBounds(0, 0, 36, 36);
		sight.setLocation(0, 300);
		sight.setVisible(false);
		add(sight);
	}

	private void setRandomBlocks() {
		/*
		 * Set blocks in bottom area in random position and color as initial
		 * elements in the game
		 */
		Random r = new Random();
		int numBlocks = 7 + Math.abs(r.nextInt(15));
		for (int i = 0; i < numBlocks; i++) {
			int x = Math.abs(r.nextInt(width - 1));
			int y = (height - 1);
			available: while (!isAvailablePosition(x, y)) {
				for (int j = 0; j < 4; j++) {
					y = y - 1;
					if (isAvailablePosition(x, y)) {
						break available;
					}
				}
				y = (height - 1);
				x = Math.abs(r.nextInt(width - 1));
			}
			Block block = new Block(x, y);
			block.stopFalling();
			addBlock(block);
		}
		revalidate();
		repaint();
	}

	public void start() {
		if (isPaused)
			return;
		isRunning = true;
		newBlocks();
		timer.start();
	}

	private void newBlocks() {
		int yr = 0;
		int xr = getPositionAvailable();
		if (xr > -1) {
			Block lBlock = new Block(xr, yr);
			Block rBlock = new Block(xr + 1, yr);
			addBlock(lBlock);
			addBlock(rBlock);
			revalidate();
			repaint();
		} else {
			timer.stop();
			statusBar.setText("GAME OVER!");
		}
	}

	private synchronized void addBlock(Block block) {
		List<Block> synList = Collections.synchronizedList(blockList);
		table[block.getY() / Block.SIZE][block.getX() / Block.SIZE] = true;
		add(block);
		synList.add(block);
		revalidate();
		repaint();
		blocks[block.getY() / Block.SIZE][block.getX() / Block.SIZE] = block;
	}

	private int getPositionAvailable() {
		Random r = new Random();
		int yr = 0;
		int xr = -1;
		if (isPositionAvailableAtX()) {
			do {
				xr = Math.abs(r.nextInt(width - 1));
			} while (!isAvailablePosition(xr, yr)
					|| !isAvailablePosition(xr + 1, yr));
		}
		return xr;
	}

	private void oneLineDown() {
		List<Block> synList = Collections.synchronizedList(blockList);
		synchronized (synList) {
			for (Block block : blockList) {
				if (block != null) {
					if (block.getY() != 480) {
						if ((block.getY() / Block.SIZE) + 1 < height
								&& isAvailablePosition(block.getX()
										/ Block.SIZE,
										(block.getY() / Block.SIZE) + 1)) {
							move(block);
						} else if (!block.isFallingFinished()) {
							block.stopFalling();
							// Element candidate = new Element(block);
							// candidates.add(candidate);
							checkSides(block);
							// candidate = null;
						}
					}

				}
			}
		}
		repaint();
	}

	private void checkSides(Block block) {
		List<Block> removeList = new CopyOnWriteArrayList<Block>(blockList);
		int right = 0;
		int left = 0;
		int up = 0;
		int down = 0;
		int x = (block.getX() / Block.SIZE) + 1;
		int y = block.getY() / Block.SIZE;
		for (; x < width; x++) {
			Block s = getBlockFromArray(x, y);
			if (s != null && s.getColor().equals(block.getColor())
					&& s.isFallingFinished()) {
				right++;
			} else {
				break;
			}
		}
		x = (block.getX() / Block.SIZE) - 1;
		for (; x > -1; x--) {
			Block s = getBlockFromArray(x, y);
			if (s != null && s.getColor().equals(block.getColor())
					&& s.isFallingFinished()) {
				left++;
			} else {
				break;
			}
		}
		x = (block.getX() / Block.SIZE);
		y = block.getY() / Block.SIZE + 1;
		for (; y < height; y++) {
			Block s = getBlockFromArray(x, y);
			if (s != null && s.getColor().equals(block.getColor())
					&& s.isFallingFinished()) {
				down++;
			} else {
				break;
			}
		}
		
		
		y = block.getY() / Block.SIZE;
		x = block.getX() / Block.SIZE;
		if (right >= 3) {
			for (int r = 0; r < right + 1 && r < 4; r++) {				
				removeBlock(blocks[y][x + r]);				
			}			
			points = points + 10;
		} 
		block = null;
		statusBar.setText(String.valueOf(points));
	}

	private void removeBlock(Block block) {
		int y = block.getY() / Block.SIZE;
		int x = block.getX() / Block.SIZE;
		table[y][x] = false;					
		blocks[y][x] = null;
		remove(block);
		block = null;		
		revalidate();
		repaint();
	}

	private boolean isHLineShape(Element candidate) {
		return candidate.getHBlocks().size() > 2;
	}

	private boolean isVLineShape(Element candidate) {
		return candidate.getVBlocks().size() > 2;
	}

	private void removeShape(Element candidate, Shape shape) {
		List<Block> removeList = new CopyOnWriteArrayList<Block>(blockList);
		int x = 0;
		int y = 0;
		Block block = candidate.getBlock();
		switch (shape) {
		case H_LINE:
			List<Block> blocksLine = candidate.getHBlocks();
			for (int i = 0; i < 3; i++) {
				Block b = blocksLine.get(i);
				x = b.getX() / Block.SIZE;
				y = b.getY() / Block.SIZE;
				table[y][x] = false;
				b.setX(0);
				b.setY(480);
				removeList.remove(b);
				remove(b);
				this.blocks[y][x] = null;
			}
			x = block.getX() / Block.SIZE;
			y = block.getY() / Block.SIZE;
			table[y][x] = false;

			removeList.remove(block);
			remove(block);
			block = null;
			candidate.setBlock(null);
			revalidate();
			repaint();
			blocks[y][x] = null;
			break;
		case V_LINE:
			blocksLine = candidate.getVBlocks();
			for (int i = 0; i < 3; i++) {
				Block b = blocksLine.get(i);
				x = b.getX() / Block.SIZE;
				y = b.getY() / Block.SIZE;
				table[y][x] = false;
				b.setX(0);
				b.setY(480);
				removeList.remove(b);
				remove(b);
				revalidate();
				repaint();
				this.blocks[y][x] = null;
			}
			x = block.getX() / Block.SIZE;
			y = block.getY() / Block.SIZE;
			table[y][x] = false;

			removeList.remove(block);
			remove(block);
			block = null;
			candidate.setBlock(null);
			revalidate();
			repaint();
			blocks[y][x] = null;
			break;
		}
	}

	private void move(Block block) {
		blocks[block.getY() / Block.SIZE][block.getX() / Block.SIZE] = null;
		table[block.getY() / Block.SIZE][block.getX() / Block.SIZE] = false;
		block.addY(Block.SIZE);
		blocks[block.getY() / Block.SIZE][block.getX() / Block.SIZE] = block;
		table[block.getY() / Block.SIZE][block.getX() / Block.SIZE] = true;
	}

	private void selectBlock() {
		if (table[(curY / Block.SIZE)][curX / Block.SIZE]) {
			blockSelected = blocks[(curY / Block.SIZE)][curX / Block.SIZE];
			System.out.println("blockSelected " + blockSelected.getColor());
			if (blockSelected != null && blockSelected.isFallingFinished()) {
				sight.changeCursor();
			} else {
				blockSelected = null;
			}
		}
	}

	private void unSelectBlock() {
		sight.changeCursor();
		blockSelected = null;
	}

	private void moveSight(Direction direction) {
		sight.setVisible(true);
		switch (direction) {
		case UP:
			if (curY >= Block.SIZE) {
				curY = curY - Block.SIZE;
				sight.setY(curY);
			}
			break;
		case RIGHT:
			if (curX < (width - 1) * Block.SIZE) {
				curX = curX + Block.SIZE;
				sight.setX(curX);
			}
			break;
		case DOWN:
			if (curY < (height - 1) * Block.SIZE) {
				curY = curY + Block.SIZE;
				sight.setY(curY);
			}
			break;
		case LEFT:
			if (curX >= Block.SIZE) {
				curX = curX - Block.SIZE;
				sight.setX(curX);
			}
			break;
		}
		revalidate();
		repaint();
	}

	private void tryChange(Block block, Direction direction) {
		Block tmp;
		if (block != null) {
			int tmpX = 0;
			int tmpY = 0;
			switch (direction) {
			case UP:
				tmpX = curX / Block.SIZE;
				tmpY = (curY / Block.SIZE) - 1;
				break;
			case RIGHT:
				tmpX = (curX / Block.SIZE) + 1;
				tmpY = curY / Block.SIZE;
				break;
			case DOWN:
				tmpX = curX / Block.SIZE;
				tmpY = (curY / Block.SIZE) + 1;
				break;
			case LEFT:
				tmpX = (curX / Block.SIZE) - 1;
				tmpY = curY / Block.SIZE;
				break;
			}
			if (isValidPosition(tmpX, tmpY)) {
				if (!isAvailablePosition(tmpX, tmpY)) {
					Block tmpBlock = changeBlock(block, tmpY, tmpX);
					checkSides(block);
					checkSides(tmpBlock);
				}
			}
			revalidate();
			repaint();
			unSelectBlock();
		}
	}

	private Block changeBlock(Block block, int y, int x) {
		Block tmp = blocks[y][x];
		int tmpX = tmp.getX();
		int tmpY = tmp.getY();
		tmp.setX(new Integer(block.getX()));
		tmp.setY(new Integer(block.getY()));
		block.setX(tmpX);
		block.setY(tmpY);
		blocks[block.getY() / Block.SIZE][block.getX() / Block.SIZE] = block;
		blocks[tmp.getY() / Block.SIZE][tmp.getX() / Block.SIZE] = tmp;
		return tmp;
	}

	private void pause() {
		if (!isRunning)
			return;

		isPaused = !isPaused;
		if (isPaused) {
			timer.stop();
			keyTimer.stop();
			newsTimer.stop();
			statusBar.setText("PAUSED");
		} else {
			timer.start();
			keyTimer.start();
			newsTimer.start();
			statusBar.setText(String.valueOf(points));
		}
		repaint();

	}

	private boolean isValidPosition(int x, int y) {
		return (x >= 0 && x <= width - 1 && y >= 0 && y <= height - 1);
	}

	private boolean isAvailablePosition(int x, int y) {
		return !table[y][x];
	}

	private boolean isPositionAvailableAtX() {
		for (int i = 0; i < width - 2; i++) {
			if (isAvailablePosition(i, 0) && isAvailablePosition(i + 1, 0))
				return true;
		}
		return false;
	}

	private Block getBlockFromArray(int x, int y) {
		if (isValidPosition(x, y)) {
			return blocks[y][x];
		}
		return null;
	}

	public void actionPerformed(ActionEvent e) {
		repaint();
	}

	class GAdapter extends KeyAdapter {

		public void keyPressed(KeyEvent e) {
			int keyCode = e.getKeyCode();

			if (keyCode == 'p' || keyCode == 'P') {
				pause();
			}

			if (isPaused) {
				return;
			}

			switch (keyCode) {
			case 's':
				selectBlock();
				break;
			case 'S':
				selectBlock();
				break;
			case KeyEvent.VK_UP:
				tryChange(blockSelected, Direction.UP);
				moveSight(Direction.UP);
				break;
			case KeyEvent.VK_RIGHT:
				tryChange(blockSelected, Direction.RIGHT);
				moveSight(Direction.RIGHT);
				break;
			case KeyEvent.VK_DOWN:
				tryChange(blockSelected, Direction.DOWN);
				moveSight(Direction.DOWN);
				break;
			case KeyEvent.VK_LEFT:
				tryChange(blockSelected, Direction.LEFT);
				moveSight(Direction.LEFT);
				break;
			}			
		}
	}
}
